package com.afdor.rws.core.extension.spring;

import com.afdor.rws.annotation.extension.Extension;
import com.afdor.rws.core.extension.spring.annotation.ExtensionComponentScan;
import com.afdor.rws.core.extension.utils.ExtensionUtils;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;


/**
 * 扩展点扫描器
 *
 * @author 悭梵
 * @date Created in 2018-09-06 11:12
 */
@Slf4j
@ToString
@EqualsAndHashCode
@NoArgsConstructor
public class ExtensionBeanDefinitionRegistrar extends AnnotationBeanNameGenerator implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    private boolean registerBean = Boolean.TRUE;


    public void setRegisterBean(boolean registerBean) {
        this.registerBean = registerBean;
    }

    @Override
    protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
        try {
            Class implClass = Class.forName(annotatedDef.getBeanClassName());
            Class[] interfaceClasses = implClass.getInterfaces();

            AnnotationMetadata metadata = annotatedDef.getMetadata();
            Set<String> types = metadata.getAnnotationTypes();
            String beanName = null, simpleBeanName = null;
            for (String type : types) {
                AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(type, false));
                if (attributes != null && isStereotypeWithNameValue(type, metadata.getMetaAnnotationTypes(type), attributes)) {
                    Object value = attributes.get("value");
                    if (value instanceof String) {
                        String strVal = (String) value;
                        if (StringUtils.hasLength(strVal)) {
                            if (StringUtils.hasLength(beanName) && StringUtils.hasLength(simpleBeanName) && !Objects.equals(strVal, simpleBeanName)) {
                                throw new IllegalStateException("Stereotype annotations suggest inconsistent component names: '" + beanName + "' versus '" + strVal + "'");
                            }
                            simpleBeanName = strVal;
                            // 实现接口
                            Class interfaceClass = null;
                            value = attributes.get("interfaceClass");
                            if (value != null && value instanceof Class) {
                                interfaceClass = (Class) value;
                            }
                            if (interfaceClass == null || Objects.equals(Object.class, interfaceClass)) {
                                interfaceClass = interfaceClasses[0];
                            } else {
                                boolean existsInterfaceClass = false;
                                for (Class clazz : interfaceClasses) {
                                    if (Objects.equals(clazz, interfaceClass) || interfaceClass.isAssignableFrom(clazz)) {
                                        existsInterfaceClass = true;
                                        break;
                                    }
                                }
                                if (!existsInterfaceClass) {
                                    throw new IllegalStateException(" not found class " + annotatedDef.getBeanClassName() + " interface ");
                                }
                            }
                            beanName = ExtensionUtils.getFullExtension(interfaceClass, simpleBeanName);
                        }
                    }
                }
            }
            return beanName;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    protected boolean isStereotypeWithNameValue(String annotationType, Set<String> metaAnnotationTypes, Map<String, Object> attributes) {
        return (Objects.equals(Extension.class.getName(), annotationType) && attributes != null && attributes.containsKey("value") && attributes.containsKey("interfaceClass"));
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        // AnnotationConfigApplicationContext
        this.resourceLoader = resourceLoader;
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ExtensionComponentScan.class.getName()));
        ExtensionScanner scanner = new ExtensionScanner(registry);
        scanner.setBeanNameGenerator(this);

        // this check is needed in Spring 3.1
        if (resourceLoader != null) {
            scanner.setResourceLoader(resourceLoader);
        }


//        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
//        if (!BeanNameGenerator.class.equals(generatorClass)) {
//            scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
//        }

        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        scanner.registerFilters();
        processBeanDefinitions(scanner.doScan(StringUtils.toStringArray(basePackages)));
    }

    protected void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition = null;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            definition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);


            try {
                Class<?> implClass = Class.forName(definition.getBeanClassName());
                if (implClass.isAnnotationPresent(Extension.class)) {
                    Extension extension = implClass.getAnnotation(Extension.class);
                    Extension.Scope scope = extension.scope();
                    switch (scope) {
                        case PROTOTYPE:
                            definition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
                            break;
                    }
                }
            } catch (Exception e) {
            }
        }
    }


}
